home *** CD-ROM | disk | FTP | other *** search
- /*
- File: StreamLogWatcher.c
-
- Contains: A program to display information logged to the STREAMS log module.
-
- Written by: Quinn "The Eskimo!"
-
- Copyright: © 1998 by Apple Computer, Inc., all rights reserved.
-
- Change History (most recent first):
-
- You may incorporate this sample code into your applications without
- restriction, though the sample code has been provided "AS IS" and the
- responsibility for its operation is 100% yours. However, what you are
- not permitted to do is to redistribute the source as "DSC Sample Code"
- after having made changes. If you're going to re-distribute the source,
- we require that you make it clear in the source that the code was
- descended from Apple Sample Code, but that you've made changes.
- */
-
- #define qDebug 1
-
- /////////////////////////////////////////////////////////////////////
- // Standard C stuff.
-
- #import <stdio.h>
-
- /////////////////////////////////////////////////////////////////////
- // Pick up standard OT APIs.
-
- #import <OpenTransport.h>
-
- /////////////////////////////////////////////////////////////////////
- // Pick up the main ASLM APIs.
-
- #if __MC68K__
-
- // ^%$**(#)%( ASLM defines it's own prototype for strcpy,
- // even though it includes "string.h". This conflicts with the way
- // CodeWarrior does inline strcpy in MSL in 68K. Argh! So
- // I've done an evil C pre-processor hacks to get around this problem
- // just for the sake of getting this project to compile out of the box.
- // The real solution is to edit out the extra prototype for strcpy
- // in your copy of "LibraryManager".
-
- #import <string.h>
-
- #undef strcpy
- #define strcpy BogusStrCpy
-
- #import <LibraryManager.h>
-
- #undef strcpy
- #define strcpy(dst, src) __strcpy(dst, src)
-
- #else
- #import <LibraryManager.h>
- #endif
-
- /////////////////////////////////////////////////////////////////////
- // Pick up the low-level OT APIs.
-
- #import <OTDebug.h>
-
- /////////////////////////////////////////////////////////////////////
- // Pick up standard toolbox APIs.
-
- #import <QuickDraw.h>
- #import <Fonts.h>
- #import <Windows.h>
- #import <Menus.h>
- #import <TextEdit.h>
- #import <Dialogs.h>
- #import <Memory.h>
- #import <Resources.h>
- #import <TextUtils.h>
- #import <Scrap.h>
- #import <Devices.h>
- #import <CodeFragments.h>
- #import <Gestalt.h>
- #import <Processes.h>
-
- /////////////////////////////////////////////////////////////////////
- // Pick up less-standard toolbox APIs.
-
- #import "InternetConfig.h"
-
- /////////////////////////////////////////////////////////////////////
- // Lots of useful subroutines from the Internet Config libraries.
-
- #import "ICDialogs.h"
- #import "ICMiscSubs.h"
- #import "ICCommonSubs.h"
-
- /////////////////////////////////////////////////////////////////////
- // Pick up our resource definitions.
-
- #import "StreamLogResources.h"
-
- /////////////////////////////////////////////////////////////////////
- // Pick up the file logging stuff.
-
- #import "FileLogging.h"
-
- /////////////////////////////////////////////////////////////////////
- // Pick up the log engine prototypes.
-
- #import "LogEngine.h"
-
- /////////////////////////////////////////////////////////////////////
- // OTDebugStr is not defined in any OT header files, but it is
- // exported by the libraries, so we define the prototype here.
-
- extern pascal void OTDebugStr(const char* str);
-
- /////////////////////////////////////////////////////////////////////
- #pragma mark **** Applications Globals *****
-
- enum {
- kCreator = 'SlVw'
- };
-
- static Boolean gQuitNow;
- // Set to true to drop out of the main event loop.
-
- static DialogPtr gMainWindow = nil;
- // The main window. Pretty much the only window at
- // the moment.
-
- static ListHandle gLogList = nil;
- // The List Manager list in the main window.
-
- static ListDefUPP gListDefProcUPP;
- // A UPP for the list definition proc we use
- // for this list. We have a dummy LDEF whose
- // resource ID we supply to LNew. That LDEF just
- // calls through the list's refCon (assuming it isn't
- // nil). We poke this UPP into that refCon, and hence
- // the LDEF calls us. This allows for source
- // level debugging of the LDEF without messing around
- // with self-modifying code.
-
- static UserItemUPP gListUserItemUpdateUPP;
- // A UPP for the user item in gMainWindow that contains
- // the gLogList. This routine simply calls LUpdate
- // (oh, and also draws the frame for the list).
-
- static Rect gMainWindowGrowBounds;
- // Bounds which we pass to GrowWindow to constrain how
- // much the window can grow or shrink. The topLeft
- // co-ordinate in the minimum size (which we get from
- // the window's portRect just after we create it)
- // and the botRight are derived from screenBits.bounds
- // (which Window Manager special cases to allow windows
- // to grow as large as possible on the screen(s)).
-
- static Boolean gScrollWhileLogging = true;
- // This value is a mirror of the state of the menu
- // item. We mirror it to make it quicker to fetch while
- // we're logging.
-
- static SInt16 gModuleID; // -1 => log all modules
- static SInt16 gStreamID; // -1 => log all streams
- // These are the logging filters. This only take effect
- // if the All Traces radio button is /not/ on. The values
- // are set by means of a modal dialog.
-
- static void DirtyPreferences(void);
- // forward
- static void PreferencesIdle(void);
- // forward
-
- /////////////////////////////////////////////////////////////////////
- #pragma mark **** Apple Event Handlers *****
-
- static pascal OSErr AEOpenApplicationHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
- {
- #pragma unused(reply)
- #pragma unused(handlerRefcon)
- OSStatus err;
-
- err = AEGotRequiredParams(theAppleEvent);
- return err;
- }
-
- static pascal OSErr AEOpenDocumentsHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
- {
- #pragma unused(reply)
- #pragma unused(handlerRefcon)
- OSStatus err;
-
- err = AEGotRequiredParams(theAppleEvent);
- return err;
- }
-
- static pascal OSErr AEQuitApplicationHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
- {
- #pragma unused(reply)
- #pragma unused(handlerRefcon)
- OSStatus err;
-
- err = AEGotRequiredParams(theAppleEvent);
- if (err == noErr) {
- gQuitNow = true;
- }
- return err;
- }
-
- /////////////////////////////////////////////////////////////////////
- #pragma mark **** List Logging Stuff *****
-
- enum {
-
- // kMaxListRows is the maximum number of rows we can have in our
- // scrolling list. List Manager imposes an overall limit of 32K.
- // It also imposes a 32K limit on the amount of data in the list.
- // We use 4 bytes per row of data, which would imply this limit
- // should be 8K. 5000 is just to keep it within reasonable bounds.
-
- kMaxListRows = 5000,
-
- // When the list exceeds the above limit, we delete some rows from
- // the front of the list. We do this in chunks for efficiency's
- // sake. This is the size of the chunk we delete.
-
- kNumberOfRowsToDelete = 100
- };
-
- static LogEntryPtr ListGetLogEntryForRow(SInt16 rowNumber)
- // Given a row number (zero based), this routine returns
- // the log entry for that row. if there is no log entry
- // (which can happen if the routine is called by the LDEF
- // as the row is added), it returns nil.
- {
- Cell thisCell;
- LogEntryPtr result;
- SInt16 dataSize;
-
- thisCell.h = 0;
- thisCell.v = rowNumber;
- dataSize = sizeof(result);
- LGetCell(&result, &dataSize, thisCell, gLogList);
- if ( dataSize != sizeof(result) ) {
- result = nil;
- }
- return result;
- }
-
- static void ListSetLogEntryForRow(SInt16 rowNumber, LogEntryPtr rowEntry)
- // This routine sets the data for the given rowNumber (zero based)
- // to rowEntry, to later be retrieved by ListGetLogEntryForRow.
- {
- Cell thisCell;
-
- thisCell.h = 0;
- thisCell.v = rowNumber;
- LSetCell(&rowEntry, sizeof(rowEntry), thisCell, gLogList);
- }
-
- static void FlagsToShortString(char flags, Str255 flagStr)
- // This routine sets flagStr to an 8 character string
- // that represents flags in a very compact form, suitable
- // for drawing by the LDEF.
- {
- OTMemset(&flagStr[1], '-', 8);
- flagStr[0] = 8;
-
- if ((flags & SL_TRACE) != 0) {
- flagStr[1] = 'T';
- }
- if ((flags & SL_ERROR) != 0) {
- flagStr[2] = 'E';
- }
- if ((flags & SL_CONSOLE) != 0) {
- flagStr[3] = 'C';
- }
-
- // flagStr[4] is blank
-
- if ((flags & SL_FATAL) != 0) {
- flagStr[5] = 'F';
- }
- if ((flags & SL_NOTIFY) != 0) {
- flagStr[6] = 'N';
- }
- if ((flags & SL_WARN) != 0) {
- flagStr[7] = 'W';
- }
- if ((flags & SL_NOTE) != 0) {
- flagStr[8] = 'N';
- }
- }
-
- // These constants define the start (and hence the width)
- // of each of the fields in our LDEF.
-
- enum {
- kFieldStart0 = 4,
- kFieldStart1 = kFieldStart0 + 40,
- kFieldStart2 = kFieldStart1 + 60,
- kFieldStart3 = kFieldStart2 + 60,
- kFieldStart4 = kFieldStart3 + 60,
- kFieldStart5 = kFieldStart4 + 40,
- kFieldStart6 = kFieldStart5 + 40,
- kFieldStart7 = kFieldStart6 + 0
- };
-
- static void ListDefProcDraw(Rect *theCellRect, SInt16 rowNumber)
- // This routine is called by the LDEF in response to an
- // lDraw message. The basic idea is to get the log entry
- // for the given row and then use the given rectangle to
- // render the data in the entry.
- {
- LogEntryPtr thisEntry;
- Str255 tmpStr;
- UInt32 strLen;
-
- thisEntry = ListGetLogEntryForRow(rowNumber);
- OTAssert("ListDefProc: No data for cell", thisEntry != nil);
- if (thisEntry != nil) {
-
- NumToString(thisEntry->fLogHeader.seq_no, tmpStr);
- MoveTo(theCellRect->left + kFieldStart0, theCellRect->bottom - 4);
- DrawString(tmpStr);
-
- MoveTo(theCellRect->left + kFieldStart1, theCellRect->bottom - 4);
- FlagsToShortString(thisEntry->fLogHeader.flags, tmpStr);
- DrawString(tmpStr);
-
- MoveTo(theCellRect->left + kFieldStart2, theCellRect->bottom - 4);
- DateString(thisEntry->fLogHeader.ttime, shortDate, tmpStr, nil);
- DrawString(tmpStr);
-
- TimeString(thisEntry->fLogHeader.ttime, true, tmpStr, nil);
- MoveTo(theCellRect->left + kFieldStart3, theCellRect->bottom - 4);
- DrawString(tmpStr);
-
- NumToString(thisEntry->fLogHeader.mid, tmpStr);
- MoveTo(theCellRect->left + kFieldStart4, theCellRect->bottom - 4);
- DrawString(tmpStr);
-
- NumToString(thisEntry->fLogHeader.sid, tmpStr);
- MoveTo(theCellRect->left + kFieldStart5, theCellRect->bottom - 4);
- DrawString(tmpStr);
-
- // We clip strLen at 255 to avoid attempting to draw more than
- // a 255 character string (which won't work 'cause it's a Pascal
- // string). We could have used DrawText, but 255 characters is
- // enough for me. If you need more, look at the file log.
-
- strLen = thisEntry->fTextLength;
- if ( strLen > 255 ) {
- strLen = 255;
- }
- BlockMoveData((char *) thisEntry + sizeof(LogEntry), &tmpStr[1], strLen);
- tmpStr[0] = strLen;
- MoveTo(theCellRect->left + kFieldStart6, theCellRect->bottom - 4);
- DrawString(tmpStr);
- }
- }
-
- static pascal void ListDefProc(SInt16 message, Boolean drawSelected,
- Rect *theCellRect, Cell theCell,
- SInt16 dataOffset, SInt16 dataLen,
- ListRef listH)
- // Our list definition proc (LDEF). This routine is called
- // back by the List Manager when it wants to draw (or highlight)
- // a cell in the list in our main window. The implementation
- // basically dispatches based on message. Of cours, there's only
- // one basic action, so this is just a glorified wrapper for
- // ListDefProcDraw.
- {
- #pragma unused(dataOffset)
- #pragma unused(dataLen)
-
- #if qDebug
- OTAssert("ListDefProc: This LDEF only works for gLogList", listH == gLogList);
- #else
- #pragma unused(listH)
- #endif
-
- switch (message) {
- case lInitMsg:
- // do nothing
- break;
- case lDrawMsg:
- case lHiliteMsg:
- EraseRect(theCellRect);
- ListDefProcDraw(theCellRect, theCell.v);
- if (drawSelected) {
- MagicMarkerMode();
- InvertRect(theCellRect);
- }
- break;
- case lCloseMsg:
- // do nothing
- break;
- default:
- OTDebugStr("ListDefProc: Unrecognised message");
- break;
- }
- }
-
- static pascal void ListUserItemUpdateProc(WindowPtr dlg, short item)
- // A Dialog Manager user item update proc. This routine
- // is called by the Dialog Manager when it wants to draw
- // the list user item. We respond by drawing the list frame
- // and then calling through to List Manager to draw the list itself.
- {
- Rect itemRect;
-
- // This erase is critical, because List Manager is not smart
- // enough to erase the area below the defined cells when
- // the defined cells don't take up enough space to occupy the
- // entire viewable area.
-
- GetDItemRect(dlg, item, &itemRect);
- EraseRect(&itemRect);
-
- // Draw the frame.
-
- InsetRect(&itemRect, -1, -1);
- FrameRect(&itemRect);
-
- // Call List Manager to draw the list.
-
- LUpdate(dlg->clipRgn, gLogList);
- }
-
- static void RecordLogEntryToList(LogEntryPtr thisEntry)
- // This routine is called at idle time when new list entries
- // are noticed. The basic idea is to append the new entries
- // as rows at the end of the list. We also have to deal with
- // keeping the list size within bounds and scrolling the new
- // items into view if the user asked to.
- {
- SInt16 newRow;
- SInt32 i;
- LogEntryPtr anEntry;
- Cell tmpCell;
-
- OTAssert("RecordLogEntryToList: gLogList is nil", gLogList != nil);
-
- // First check to see whether we have too many rows in the list. If so,
- // delete the first kNumberOfRowsToDelete rows, making sure we release
- // the data associated with deleted rows.
-
- if ( ((**gLogList).dataBounds.bottom - (**gLogList).dataBounds.top) > kMaxListRows) {
- for (i = 0; i < kNumberOfRowsToDelete; i++) {
- anEntry = ListGetLogEntryForRow(i);
- OTAssert("RecordLogEntryToList: No list data", anEntry != nil);
- ReleaseLogEntry(anEntry);
- }
- LDelRow(kNumberOfRowsToDelete, 0, gLogList);
- }
-
- // Now add the row, and set the data for the new row.
- // Make sure we call RetainLogEntry to denote the extra
- // reference to this data.
-
- LSetDrawingMode(false, gLogList);
-
- newRow = LAddRow(1, 32767, gLogList);
- RetainLogEntry(thisEntry);
- LSetDrawingMode(true, gLogList);
- ListSetLogEntryForRow(newRow, thisEntry);
-
- tmpCell.h = 0;
- tmpCell.v = newRow;
-
- // Finally, scroll the list to show the new item if the user
- // asked us to.
-
- if ( gScrollWhileLogging ) {
- LScroll(0, newRow, gLogList);
- }
- }
-
- /////////////////////////////////////////////////////////////////////
- #pragma mark **** General User Interface Stuff *****
-
- static void SizeMainWindow(SInt16 newWidth, SInt16 newHeight)
- // This routine is used to resize the main window,
- // making sure that the list in the window is also resized
- // to match.
- {
- Rect itemRect;
-
- // Round the height to the closest 16 pixel boundary.
- // We need to make sure the list stays a multiple of
- // 16 pixels high (otherwise List Manager behaves
- // suckily), and we do this by making sure the window
- // is always a multiple of 16 pixels high.
-
- newHeight = (newHeight + 8) / 16 * 16 - 1;
-
- // Resize the window.
-
- SizeWindow(gMainWindow, newWidth, newHeight, true);
-
- // Now sync up the dialog item and the list view rect
- // with the new window size. This took some time to get
- // right.
-
- GetDItemRect(gMainWindow, ditLogEntryList, &itemRect);
- itemRect.right = gMainWindow->portRect.right;
- itemRect.bottom = gMainWindow->portRect.bottom;
- SetDItemRect(gMainWindow, ditLogEntryList, &itemRect);
- itemRect.right -= 15;
- itemRect.bottom -= 15;
- LSize(itemRect.right - itemRect.left,
- itemRect.bottom - itemRect.top,
- gLogList);
- }
-
- static void SetMainWindowVisibility(Boolean isVisible)
- // We use this routine to show and hide the main
- // window, making sure that all the UI elements that
- // depend on this state are kept in sync.
- {
- Str255 menuItemText;
-
- OTAssert("SetMainWindowVisibility: No list handle", gLogList != nil);
-
- if (isVisible) {
- ShowWindow(gMainWindow);
-
- if ( ! TitleBarOnScreen(gMainWindow) ) {
- MoveWindow(gMainWindow, 100, 100, false);
- }
-
- GetIndString(menuItemText, rMiscStrings, strHideLogWindow);
- } else {
- HideWindow(gMainWindow);
- GetIndString(menuItemText, rMiscStrings, strShowLogWindow);
- }
- SetMenuItemText( GetMenuHandle(mFile), iShowHideLogWindow, menuItemText);
- }
-
- static void SetScrollWhileLogging(Boolean scrollWhileLogging)
- // We use this routine to set the scroll while logging state,
- // keeping the UI in sync.
- {
- gScrollWhileLogging = scrollWhileLogging;
- CheckItem( GetMenuHandle(mFile), iScrollWhileLogging, gScrollWhileLogging);
- }
-
- static void DisplayError(OSStatus errNum)
- {
- Str255 becauseString;
- Str255 errNumStr;
-
- if (errNum != noErr && errNum != userCanceledErr) {
- NumToString(errNum, errNumStr);
- NewLookupErrorC(rErrorTable, errNum, becauseString);
- ParamText(becauseString, errNumStr, "\p", "\p");
- (void) StopAlert(rErrorAlert, gOKModalFilterUPP);
- }
- }
-
- static void UpdateStartStopUI(void)
- // This routine is used to sync up the enabled state of
- // the start and stop user interface to the current
- // logging state.
- {
- Str255 tmpStr;
-
- SetDControlEnable(gMainWindow, ditStop, LoggingActive());
- SetDControlEnable(gMainWindow, ditStart, ! LoggingActive());
- if ( LoggingActive() ) {
- GetIndString(tmpStr, rMiscStrings, strStopLogging);
- } else {
- GetIndString(tmpStr, rMiscStrings, strStopLogging);
- }
- SetMenuItemText(GetMenuHandle(mFile), iStartStopLogging, tmpStr);
- }
-
- static void UIStartLogging(void)
- // This routine handles the user clicking on the Start
- // logging button (or the equivalent menu command). It
- // gathers the logging parameters from the dialog items
- // and then calls the logging back end.
- {
- OSStatus err;
- UInt32 traceInfoCount;
- struct trace_ids *traceInfo;
- struct trace_ids traceInfoBuffer;
-
- // Gather the logging parameters from the dialog.
-
- traceInfoCount = 0;
- traceInfo = nil;
- if ( GetDControlBoolean(gMainWindow, ditLogTraces) ) {
- traceInfoCount = 1;
- traceInfo = &traceInfoBuffer;
-
- if ( GetDControlBoolean(gMainWindow, ditAllTraces) ) {
- traceInfoBuffer.ti_mid = -1;
- traceInfoBuffer.ti_sid = -1;
- } else {
- traceInfoBuffer.ti_mid = gModuleID;
- traceInfoBuffer.ti_sid = gStreamID;
- }
- if ( GetDControlValue(gMainWindow, ditTraceLevel) == iAll ) {
- traceInfoBuffer.ti_level = -1;
- } else {
- traceInfoBuffer.ti_level = GetDControlValue(gMainWindow, ditTraceLevel) - iLevelOffset;
- }
- }
-
- // If we're supposed to be logging to a file, start the
- // file logging module.
-
- err = noErr;
- if ( GetDControlBoolean(gMainWindow, ditLogToFile) ) {
- err = StartFileLogging();
- }
-
- // Start the back end with the parameters we've already
- // worked out.
-
- if (err == noErr) {
- err = StartLogging(GetDControlBoolean(gMainWindow, ditLogErrors), traceInfoCount, traceInfo);
- }
-
- // Update the user interface to reflect the change
- // of logging state.
-
- if ( err == noErr ) {
- UpdateStartStopUI();
- }
-
- // Clean up.
-
- if (err != noErr) {
- if ( FileLoggingActive() ) {
- StopFileLogging();
- }
- }
-
- DisplayError(err);
- }
-
- static void DoIdle(void);
- // forward
-
- static void UIStopLogging(void)
- // This routine is called in response to the user
- // clicking on the Stop logging button (or the equivalent
- // menu command). It basically calls through to the back
- // end to stop the logging process, then flushouts out
- // any remaining log entries to the log list and file,
- // then shuts down file logging if it was started up.
- {
- StopLogging();
-
- // Calling DoIdle will flush out any log entries that remain
- // in the LIFO, which is important to do before we close the
- // file.
-
- DoIdle();
-
- if ( FileLoggingActive() ) {
- StopFileLogging();
- }
- UpdateStartStopUI();
- }
-
- static Boolean UIConfirmAndStop(void)
- // A generic routine which we call whenever we have to
- // perform some action that will change a logging parameter.
- // We don't want the current logging parameters to be out
- // of sync with the user interface, and I'm too lazy to
- // support adjusting the parameters dynamically, so instead
- // we stop logging whenever the user changes a logging parameter.
- // This routine puts up an alert asking whether that's OK,
- // and then stops logging if it is. It returns true if
- // it's OK to perform a change in logging parameters (either
- // because logging was off, or because we just turned it off).
- {
- Boolean result;
-
- result = false;
- if ( LoggingActive() ) {
- if ( CautionAlert(rConfirmStopAlert, gOKCancelModalFilter) == ditOK ) {
- UIStopLogging();
- result = true;
- }
- } else {
- result = true;
- }
- return result;
- }
-
- static void UIPoseFilterDialog(void)
- // This routine puts up the dialog that lets the user
- // enter trace filtering parameters. It populates the dialog
- // based on gModuleID and gStreamID, and if the user clicks
- // OK it writes back to those globals.
- {
- DialogPtr dlg;
- Str255 tmpStr;
- SInt32 tmpLong;
- SInt16 hitItem;
-
- // Create and prepare the dialog.
-
- dlg = GetNewDialog(rFilterDialog, nil, (WindowPtr) -1);
- if (dlg != nil) {
-
- SetupDefaultButtonUserItem(dlg, ditOK, ditDefault);
-
- SetDControlBoolean(dlg, ditAllModules, gModuleID == -1);
- SetDControlBoolean(dlg, ditChosenModules, gModuleID != -1);
- if (gModuleID != -1) {
- NumToString(gModuleID, tmpStr);
- SetDItemText(dlg, ditModuleID, tmpStr);
- }
-
- SetDControlBoolean(dlg, ditAllStreams, gStreamID == -1);
- SetDControlBoolean(dlg, ditChosenStreams, gStreamID != -1);
- if (gStreamID != -1) {
- NumToString(gStreamID, tmpStr);
- SetDItemText(dlg, ditStreamID, tmpStr);
- }
-
- SelectDialogItemText(dlg, ditModuleID, 0, 32767);
-
- // Run the dialog and respond to user events.
-
- ShowWindow(dlg);
- do {
- ModalDialog(gOKCancelModalFilter, &hitItem);
- switch (hitItem) {
- case ditAllModules:
- case ditChosenModules:
- ToggleDControlBoolean(dlg, ditAllModules);
- ToggleDControlBoolean(dlg, ditChosenModules);
- break;
- case ditAllStreams:
- case ditChosenStreams:
- ToggleDControlBoolean(dlg, ditAllStreams);
- ToggleDControlBoolean(dlg, ditChosenStreams);
- break;
- case ditOK:
- case ditCancel:
- // do nothing
- break;
- default:
- OTDebugStr("UIPoseFilterDialog: Weird hitItem");
- }
- } while ( hitItem != ditOK && hitItem != ditCancel );
-
- // If OK, gather the results of the dialog and write
- // it back to the global variables.
-
- if (hitItem == ditOK) {
-
- if ( GetDControlBoolean(dlg, ditAllModules) ) {
- gModuleID = -1;
- } else {
- GetDItemText(dlg, ditModuleID, tmpStr);
- StringToNum(tmpStr, &tmpLong);
- gModuleID = tmpLong;
- }
-
- if ( GetDControlBoolean(dlg, ditAllStreams) ) {
- gStreamID = -1;
- } else {
- GetDItemText(dlg, ditStreamID, tmpStr);
- StringToNum(tmpStr, &tmpLong);
- gStreamID = tmpLong;
- }
-
- DirtyPreferences();
- }
-
- DisposeDialog(dlg);
-
- } else {
- DisplayError(memFullErr);
- }
- }
-
- /////////////////////////////////////////////////////////////////////
- #pragma mark **** Log Entry Processing *****
-
- // gLoggedCount is used to record the total number of entries
- // we have logged. We use this number to maintain a static text
- // item in the dialog.
-
- static UInt32 gLoggedCount = 0;
-
- // gLastLoggedCount and gLastDroppedCount record the last value
- // of these two variables we used to fill out the static text fields.
- // We keep a record of this so that, each time around the idle loop,
- // we don't go to the trouble of updating the static text items unless
- // they have changed.
-
- static UInt32 gLastLoggedCount = 0;
- static UInt32 gLastDroppedCount = 0;
-
- static pascal void RecordLogEntry(LogEntryPtr thisEntry, void *refCon)
- // This routine is a callback, called by the logging module (which
- // we in turn called at idle time), when a new log entry has been
- // found. We call our two logging subsystems (on screen list
- // and file) to log the entry to the appropriate places. The
- // routines we call are responsible for retaining thisEntry
- // if they need it to hang around; otherwise it will be disposed
- // when this routine returns.
- {
- #pragma unused(refCon)
-
- RecordLogEntryToList(thisEntry);
- RecordLogEntryToFile(thisEntry);
-
- gLoggedCount += 1;
- }
-
- /////////////////////////////////////////////////////////////////////
- #pragma mark **** Menu Handling *****
-
- static void AdjustMenus(void)
- // This routine adjusts the menus based on the state of the
- // program in preparation for a call to MenuKey or MenuSelect.
- {
- MenuHandle menuH;
- Boolean logAtFront;
- Boolean logHasSelection;
-
- // File
-
- menuH = GetMenuHandle(mFile);
- SetMenuItemEnable(menuH, iClose, FrontWindow() == gMainWindow );
-
- // Edit
-
- menuH = GetMenuHandle(mEdit);
- logAtFront = (FrontWindow() == gMainWindow);
- logHasSelection = (LSelectedLine(gLogList) != -1);
- DisableItem(menuH, iUndo);
- SetMenuItemEnable(menuH, iCut, logAtFront && logHasSelection);
- SetMenuItemEnable(menuH, iCopy, logAtFront && logHasSelection);
- DisableItem(menuH, iPaste);
- SetMenuItemEnable(menuH, iClear, logAtFront && logHasSelection);
- SetMenuItemEnable(menuH, iSelectAll, logAtFront && ! LIsEmpty(gLogList) );
- }
-
- static void CopySelectedListEntriesToClip(void)
- // This routine is used to implement the Copy and Cut
- // menu commands. It iterates through the list of selected
- // cells and adds the text from each of them into a handle.
- // It then puts that handle into the system scrap.
- {
- OSStatus err;
- Cell listCell;
- Handle clipTextH;
- LogEntryPtr anEntry;
- char *entryAsText;
-
- // Create a handle for the text we're copying.
-
- clipTextH = NewHandle(0);
- err = CheckMemError(clipTextH);
- if (err == noErr) {
-
- // Iterate through the selected cells, adding their
- // text representations to the handle.
-
- listCell.v = 0;
- listCell.h = 0;
- while ( LGetSelect(true, &listCell, gLogList) ) {
-
- anEntry = ListGetLogEntryForRow(listCell.v);
- OTAssert("ClearSelectedListEntries: No list data", anEntry != nil);
-
- entryAsText = LogEntryToCString(anEntry);
- err = PtrAndHand(entryAsText, clipTextH, OTStrLength(entryAsText));
- if (err != noErr) {
- break;
- }
- listCell.v += + 1;
- listCell.h = 0;
- }
- }
-
- // Put the newly created text into the system scrap.
-
- if (err == noErr) {
- (void) ZeroScrap();
- err = PutScrap( GetHandleSize(clipTextH), 'TEXT', *clipTextH );
- if (err > 0) {
- err = 0;
- }
- }
-
- // Clean up.
-
- if ( clipTextH != nil ) {
- DisposeHandle(clipTextH);
- }
- DisplayError(err);
- }
-
- static void ClearSelectedListEntries(void)
- // This routine is used to implement the Cut and Clear
- // menu commands. It iterates through the list of selected
- // cells deleting them.
- {
- Cell listCell;
- LogEntryPtr anEntry;
-
- // I turn off drawing mode, delete all the selected cells, turn
- // drawing mode back on and then invalidate the entire list. This
- // causes flashing if there's only one cell selected, but it works
- // much better in the case where there are *lots* of cells selected.
- // I chose to do it this way because I think the latter case will be
- // more prevelant, and I don't have time to handle both cases properly.
-
- LSetDrawingMode(false, gLogList);
- listCell.v = 0;
- listCell.h = 0;
- while ( LGetSelect(true, &listCell, gLogList) ) {
-
- anEntry = ListGetLogEntryForRow(listCell.v);
- OTAssert("ClearSelectedListEntries: No list data", anEntry != nil);
- ReleaseLogEntry(anEntry);
- LDelRow(1, listCell.v, gLogList);
-
- // Don't increment listCell.v because we've just deleted it.
- // LGetSelect will continue looking from the current co-ordinates,
- // which will be the next selected cell.
- // listCell.v += + 1;
- listCell.h = 0;
- }
- LSetDrawingMode(true, gLogList);
- InvalDItem(gMainWindow, ditLogEntryList);
- }
-
- static void DoMenu(SInt32 menuAndItem)
- // Handle a menu command. menuAndItme is a longint
- // with the menu number in the top 16 bits and the
- // menu item in the bottom 16, ie the format returned
- // by MenuSelect and MenuKey.
- //
- // Yeah, this should be more than one procedure (-:
- {
- SInt16 menu;
- SInt16 item;
- UInt32 startTicks;
- SInt32 junkLong;
- Str255 daName;
- SInt16 junk;
- VersRecHndl versH;
- SInt8 s;
-
- startTicks = TickCount();
-
- menu = menuAndItem >> 16;
- item = menuAndItem & 0x0FFFF;
- switch (menu) {
- case mApple:
- switch (item) {
- case iAbout:
- versH = (VersRecHndl) Get1Resource('vers', 1);
- OTAssert("", versH != nil);
- s = HGetState( (Handle) versH );
- HLock( (Handle) versH );
- ParamText( (**versH).shortVersion, "\p", "\p", "\p");
- junk = Alert(rAboutBox, gOKModalFilterUPP);
- HSetState( (Handle) versH, s );
- break;
- default:
- GetMenuItemText(GetMenuHandle(mApple), item, daName);
- junk = OpenDeskAcc(daName);
- break;
- }
- break;
- case mFile:
- switch (item) {
- case iClose:
- SetMainWindowVisibility(false);
- DirtyPreferences();
- break;
- case iShowHideLogWindow:
- SetMainWindowVisibility( ! ((WindowPeek) gMainWindow)->visible );
- DirtyPreferences();
- break;
- case iStartStopLogging:
- if ( LoggingActive() ) {
- FlashDItem(gMainWindow, ditStop);
- UIStopLogging();
- } else {
- FlashDItem(gMainWindow, ditStart);
- UIStartLogging();
- }
- break;
- case iScrollWhileLogging:
- SetScrollWhileLogging( ! gScrollWhileLogging);
- DirtyPreferences();
- break;
- case iQuit:
- gQuitNow = true;
- break;
- default:
- OTDebugStr("DoMenu: Weird File menu item");
- break;
- }
- break;
- case mEdit:
- OTAssert("DoMenu: Edit menu should be disabled if gMainWindow not at front", gMainWindow == FrontWindow());
- switch (item) {
- case iCut:
- CopySelectedListEntriesToClip();
- ClearSelectedListEntries();
- break;
- case iCopy:
- CopySelectedListEntriesToClip();
- break;
- case iClear:
- ClearSelectedListEntries();
- break;
- case iSelectAll:
- LSelectAll(gLogList);
- break;
- default:
- OTDebugStr("DoMenu: Weird Edit menu item");
- break;
- }
- default:
- // OTDebugStr("DoMenu: Weird menu item");
- break;
- }
-
- // Unhighlight the menu, delaying to avoid a quick flash.
-
- if ( ! gQuitNow ) {
- while ( TickCount() < startTicks + 6 ) {
- Delay(1, &junkLong);
- }
- HiliteMenu(0);
- }
- }
-
- /////////////////////////////////////////////////////////////////////
- #pragma mark **** Low Level Event Handling *****
-
- static void DoMouseDown(EventRecord *event)
- // Handle a mouse down event. This is standard
- // Mac OS stuff. Call FindWindow to determine what
- // type and event it was (and what window it happened in)
- // and then handle each case.
- {
- SInt16 partCode;
- WindowPtr hitWindow;
- long growResult;
-
- partCode = FindWindow(event->where, &hitWindow);
- switch ( partCode ) {
- case inGoAway:
- if ( TrackGoAway(hitWindow, event->where) ) {
- if ( hitWindow == gMainWindow ) {
- SetMainWindowVisibility(false);
- DirtyPreferences();
- } else {
- OTDebugStr("DoMouseDown: Does not handle closing other windows");
- }
- }
- break;
- case inMenuBar:
- AdjustMenus();
- DoMenu(MenuSelect(event->where));
- break;
- case inDrag:
- DragWindow(hitWindow, event->where, &qd.screenBits.bounds);
- DirtyPreferences();
- break;
- case inGrow:
- growResult = GrowWindow(hitWindow, event->where, &gMainWindowGrowBounds);
- if ( growResult != 0 ) {
- Rect tmpRect;
-
- if ( hitWindow == gMainWindow ) {
- SizeMainWindow(growResult, growResult >> 16);
- DirtyPreferences();
- } else {
- SizeWindow(hitWindow, growResult, growResult >> 16, true);
- }
-
- tmpRect = hitWindow->portRect;
- tmpRect.top = tmpRect.bottom - 15;
- InvalRect(&tmpRect);
- tmpRect = hitWindow->portRect;
- tmpRect.left = tmpRect.right - 15;
- InvalRect(&tmpRect);
- }
- break;
- case inZoomIn:
- case inZoomOut:
- if ( TrackBox(hitWindow, event->where, partCode) ) {
- ZoomWindow(hitWindow, partCode, false);
- if (hitWindow == gMainWindow) {
- SizeMainWindow(hitWindow->portRect.right - hitWindow->portRect.left,
- hitWindow->portRect.bottom - hitWindow->portRect.top);
- }
-
- // Force everything to redraw. I know this isn't perfect,
- // but I'd rather have window zooming working but flashy
- // than not have it.
-
- EraseRect(&hitWindow->portRect);
- InvalRect(&hitWindow->portRect);
- }
- break;
- default:
- // do nothing
- break;
- }
- }
-
- static void DoIdle(void)
- // Called each time through the main event loop. This routine
- // handles getting the new log entries out of the log module
- // and into RecordLogEntry, which sends them to the file and list
- // logging code. It also idles the file and preferences modules,
- // which delay actions for a time to prevent unnecessary disk thrashing.
- {
- Str255 tmpStr;
- UInt32 droppedCount;
-
- // Don't ask me why, or I'll start to whimper.
-
- OTIdle();
-
- // OK, I'll explain. Basically there's a weird concurrency
- // hole in OT such that, if you don't have any other network
- // activity, strlog messages will never get delivered
- // to the client. I've reported this as a bug [Radar ID •••],
- /// but it's easy to work around (calling OTIdle will deliver
- // the messages) and it's so deep in the OT kernel that
- // I'm not sure anyone will ever want to take the risk
- // associated with fixing it.
-
- ForEachNewLogEntry(RecordLogEntry, nil);
-
- FileLoggingIdle();
-
- PreferencesIdle();
-
- // Update the static text to reflect the number
- // of logs we've seen and/or dropped.
-
- if ( gLoggedCount != gLastLoggedCount ) {
- NumToString(gLoggedCount, tmpStr);
- SetDItemText(gMainWindow, ditLoggedCount, tmpStr);
- gLastLoggedCount = gLoggedCount;
- }
-
- droppedCount = NumberOfDroppedLogEntries();
- if ( droppedCount != gLastDroppedCount ) {
- NumToString(droppedCount, tmpStr);
- SetDItemText(gMainWindow, ditDroppedCount, tmpStr);
- gLastDroppedCount = droppedCount;
- }
- }
-
- static pascal void GetLogListCellText(ListHandle listH, Cell listCell, Str255 cellText)
- // This routine is a callback from LDoKey to get the text of
- // a cell. We return the sequence number for the log entry.
- // This allows LDoKey to implement "select by typing".
- {
- #pragma unused(listH)
- LogEntryPtr thisEntry;
-
- cellText[0] = 0;
-
- thisEntry = ListGetLogEntryForRow(listCell.v);
- OTAssert("GetLogListCellText: No data for cell", thisEntry != nil);
- if (thisEntry != nil) {
- NumToString(thisEntry->fLogHeader.seq_no, cellText);
- }
- }
-
- static void DoUpdateEvent(EventRecord *event)
- // In general we handle update events through
- // the Dialog Manager DialogSelect routine. However,
- // for the main window, we have to manually draw the
- // grow box. This is not as easy as it might seem, because
- // we don't want the silly fake scroll bars in our window.
- // We we set the clip region to the bottom right 15 x 15
- // rectangle, so we get the grow box and nothing else.
- //
- // Of course, Mac OS 8 Appearance does silly things here
- // too, because the grow box is now part of the window
- // structure region. But it recognises the call to DrawGrowIcon
- // and does The Right Thing (tm).
- {
- RgnHandle tmpRgn;
- Rect growRect;
-
- if ( (WindowPtr) event->message == gMainWindow ) {
- tmpRgn = NewRgn();
- OTAssert("MainEventLoop: Could not create temporary region", tmpRgn != nil);
-
- CopyRgn(gMainWindow->clipRgn, tmpRgn);
- growRect = gMainWindow->portRect;
- growRect.top = growRect.bottom - 15;
- growRect.left = growRect.right - 15;
- ClipRect(&growRect);
-
- DrawGrowIcon( (WindowPtr) event->message );
-
- SetClip(tmpRgn);
- DisposeRgn(tmpRgn);
- }
- }
-
- static void DoMainWindowHit(EventRecord *event, SInt16 hitItem, SInt16 oldTraceLevel)
- // Handle a hit on an item in the main dialog window.
- // A big switch state with a bunch of UI twiddling
- // that, if we had a real windowing toolbox, would not
- // be necessary!
- //
- // oldTraceLevel is the old value of the trace level popup
- // menu. The MainEventLoop saves this value before calling
- // DialogSelect. DialogSelect will change the popup and
- // tells us that it's done so. We then ask the user to
- // confirm whether they want to stop logging. If they say
- // they don't, we reset the popup back to the old value.
- {
- Point localWhere;
-
- switch ( hitItem ) {
- case ditStart:
- UIStartLogging();
- break;
- case ditStop:
- UIStopLogging();
- break;
-
- case ditLogErrors:
- case ditLogTraces:
- case ditLogToFile:
- if ( UIConfirmAndStop() ) {
- ToggleDControlBoolean(gMainWindow, hitItem);
- DirtyPreferences();
- }
- break;
-
- case ditAllTraces:
- case ditTracesMatching:
- if ( UIConfirmAndStop() ) {
- SetDControlBoolean(gMainWindow, ditAllTraces, hitItem == ditAllTraces);
- SetDControlBoolean(gMainWindow, ditTracesMatching, hitItem == ditTracesMatching);
- SetDControlEnable(gMainWindow, ditSetupFilter, hitItem == ditTracesMatching);
- DirtyPreferences();
- }
- break;
-
- case ditSetupFilter:
- if ( UIConfirmAndStop() ) {
- UIPoseFilterDialog();
- }
- break;
-
- case ditTraceLevel:
- if ( UIConfirmAndStop() ) {
- DirtyPreferences();
- } else {
- SetDControlValue(gMainWindow, ditTraceLevel, oldTraceLevel);
- }
- break;
-
- case ditLogEntryList:
- localWhere = event->where;
- GlobalToLocal(&localWhere);
- (void) LClick(localWhere, event->modifiers, gLogList);
- break;
- default:
- OTDebugStr("DoMainWindowHit: Unexpected hitItem");
- break;
- }
- }
-
- static void MainEventLoop(void)
- {
- EventRecord event;
- WindowRef hitWindow;
- SInt16 hitItem;
- SInt16 oldTraceLevel;
-
- gQuitNow = false;
- do {
- DoIdle();
-
- (void) WaitNextEvent(everyEvent, &event, 60, nil);
-
- SetPort(gMainWindow);
-
- if ( IsDialogEvent(&event) ) {
- oldTraceLevel = GetDControlValue(gMainWindow, ditTraceLevel);
- if ( DialogSelect(&event, &hitWindow, &hitItem) ) {
- if (hitWindow == gMainWindow) {
- DoMainWindowHit(&event, hitItem, oldTraceLevel);
- } else {
- OTDebugStr("MainEventLoop: What other dialogs?");
- }
- }
- }
- switch (event.what) {
- case keyDown:
- case autoKey:
- if ( event.what == keyDown && (event.modifiers & cmdKey) != 0 ) {
- AdjustMenus();
- DoMenu(MenuKey(event.message & charCodeMask));
- } else if ( FrontWindow() == gMainWindow) {
- UInt8 typedChar;
-
- typedChar = (event.message & charCodeMask);
- if ( typedChar == kClearChar ||
- typedChar == kBackSpaceChar ||
- typedChar == kDelChar ) {
- ClearSelectedListEntries();
- } else {
- LDoKey(gLogList, &event, GetLogListCellText);
- }
- }
- break;
- case mouseDown:
- DoMouseDown(&event);
- break;
- case updateEvt:
- DoUpdateEvent(&event);
- break;
- case activateEvt:
- if ( (WindowPtr) event.message == gMainWindow ) {
- LActivate( (event.modifiers & 1) != 0, gLogList);
- }
- break;
- case kHighLevelEvent:
- (void) AEProcessAppleEvent(&event);
- break;
- default:
- // do nothing
- break;
- }
- } while ( ! gQuitNow );
-
- // As we're quitting, we'd better shut down the logging
- // systems.
-
- if ( LoggingActive() ) {
- FlashDItem(gMainWindow, ditStop);
- UIStopLogging();
- }
- }
-
- /////////////////////////////////////////////////////////////////////
- #pragma mark **** Prefs Management *****
-
- enum {
- kPrefsMagic = kCreator
- };
-
- // PrefsRecord is stored in Internet Config (if it is
- // installed) because it's a handy preferences system
- // and I really don't want to create yet another file
- // in the Preferences folder. Because it's stored
- // on the disk, it has to be 68K aligned (becasue it might
- // be written by a 68K and read by a PPC, or vice versa).
- //
- // The fields in the record pretty much mirror the state
- // in the user interface elements. We save all this state
- // so the user (namely me during testing!) doesn't have to
- // reset it each time they launch the program.
-
- #pragma options align=mac68k
- struct PrefsRecord {
- OSType magic; // kPrefsMagic
- Rect mainWindowPosition;
- SInt16 moduleID;
- SInt16 streamID;
- Boolean logToFile;
- Boolean logErrors;
- Boolean logTraces;
- Boolean tracesMatching;
- Boolean mainWindowVisible;
- Boolean scrollWhileLogging;
- };
- typedef struct PrefsRecord PrefsRecord, *PrefsRecordPtr;
- #pragma options align=reset
-
- static UInt8 *gICPrefsKey = "\p536C5677•PrefsRecord";
- // The Internet Config key we use to save the PrefsRecord.
-
- static ICInstance gICInstance = nil;
- // Our connection to Internet Config. Note that
- // gICInstance != nil implies that it's safe to call IC.
-
- enum {
- kPrefChangeWriteDelay = 600,
- // The length of time between when the preferences
- // are dirtied and when we write them. This allows
- // multiple preference writes to be collated into one,
- // thereby saving some disk thrashing.
-
- kPrefsCleanTicks = (UInt32) -(kPrefChangeWriteDelay+1)
- // When the preferences are clean we set gTicksOfLastPrefChange
- // to this value to prevent further writes from happening.
- // This corresponds to "far in the future".
- };
-
- static UInt32 gTicksOfLastPrefChange = kPrefsCleanTicks;
- // The TickCount time at which we last modified the
- // preferences. If TickCount is more than this value plus
- // kPrefChangeWriteDelay, we need to save the preferences now.
-
- static void RestorePreferences(void)
- // Restore the preferences from Internet Config,
- // and put all the saved values back into the appropriate
- // state holder in the program.
- {
- OSStatus err;
- PrefsRecord prefs;
- SInt32 prefsSize;
- ICAttr junkAttr;
-
- OTAssert("RestorePreferences: Need a main window to do this", gMainWindow != nil);
- OTAssert("RestorePreferences: Need a log list to do this", gLogList != nil);
-
- // Start by creating a connection to IC, only if it's present
- // (and we linked to it).
-
- err = noErr;
- #if GENERATINGCFM
- if ( (void *) ICStart == (void *) kUnresolvedCFragSymbolAddress ) {
- err = -1;
- }
- #endif
- if (err == noErr) {
- err = ICStart(&gICInstance, kCreator);
- }
- if (err == noErr) {
- err = ICFindConfigFile(gICInstance, 0, nil);
- }
-
- // Then read the preferencs from IC. Ignore truncated errors.
- // It probably means that the preferences were last written by
- // a more modern version of this program, but it should damn well
- // make sure that our fields are the first fields of its preferences
- // record.
-
- if (err == noErr) {
- prefsSize = sizeof(PrefsRecord);
- err = ICGetPref(gICInstance, gICPrefsKey, &junkAttr, (void *) &prefs, &prefsSize);
- // err = -666;
- if (err == icTruncatedErr) {
- prefsSize = sizeof(PrefsRecord);
- err = noErr;
- }
- }
-
- // Basical sanity checks.
-
- if (err == noErr && prefsSize < sizeof(PrefsRecord)) {
- err = -2;
- }
- if (err == noErr && prefs.magic != kPrefsMagic) {
- err = -3;
- }
-
- // If we could not read preferences, use defaults instead.
-
- if (err != noErr) {
- prefs.magic = kPrefsMagic;
- GetWindowRect(gMainWindow, &prefs.mainWindowPosition);
- prefs.moduleID = 0;
- prefs.streamID = 0;
- prefs.logToFile = false;
- prefs.logErrors = true;
- prefs.logTraces = true;
- prefs.tracesMatching = false;
- prefs.mainWindowVisible = true;
- prefs.scrollWhileLogging = true;
- }
-
- // Now set up the program state from the preferences.
-
- gModuleID = prefs.moduleID;
- gStreamID = prefs.streamID;
-
- SetDControlBoolean(gMainWindow, ditLogToFile, prefs.logToFile);
- SetDControlBoolean(gMainWindow, ditLogErrors, prefs.logErrors);
- SetDControlBoolean(gMainWindow, ditLogTraces, prefs.logTraces);
- SetDControlBoolean(gMainWindow, ditTracesMatching, prefs.tracesMatching);
- SetDControlBoolean(gMainWindow, ditAllTraces, ! prefs.tracesMatching);
-
- SetDControlEnable(gMainWindow, ditSetupFilter, prefs.tracesMatching);
-
- MoveWindow(gMainWindow, prefs.mainWindowPosition.left, prefs.mainWindowPosition.top, false);
- SizeMainWindow(prefs.mainWindowPosition.right - prefs.mainWindowPosition.left,
- prefs.mainWindowPosition.bottom - prefs.mainWindowPosition.top);
- SetMainWindowVisibility(prefs.mainWindowVisible);
- SetScrollWhileLogging(prefs.scrollWhileLogging);
- }
-
- static void SavePreferences(void)
- // Save the preferences to Internet Config.
- {
- OSStatus junk;
- PrefsRecord prefs;
-
- // Don't even think about this if we don't have a connection
- // to IC.
-
- if ( gICInstance != nil ) {
-
- // Fill out the PrefsRecord from the program state
- // and write it out.
-
- prefs.magic = kPrefsMagic;
- GetWindowRect(gMainWindow, &prefs.mainWindowPosition);
- prefs.moduleID = gModuleID;
- prefs.streamID = gStreamID;
- prefs.logToFile = GetDControlBoolean(gMainWindow, ditLogToFile);
- prefs.logErrors = GetDControlBoolean(gMainWindow, ditLogErrors);
- prefs.logTraces = GetDControlBoolean(gMainWindow, ditLogTraces);
- prefs.tracesMatching = GetDControlBoolean(gMainWindow, ditTracesMatching);
- prefs.mainWindowVisible = ((WindowPeek) gMainWindow)->visible;
- prefs.scrollWhileLogging = gScrollWhileLogging;
-
- junk = ICSetPref(gICInstance, gICPrefsKey, ICattr_no_change, (void *) &prefs, sizeof(PrefsRecord));
- OTAssert("SavePreferences: Error writing prefs", junk == noErr);
- }
- gTicksOfLastPrefChange = kPrefsCleanTicks;
- }
-
- static void DirtyPreferences(void)
- // Called by the general code when something that is
- // stored in the prefs is modified. We note the time
- // this happens, and the idle function should eventually
- // notice this and call SavePreferences.
- {
- gTicksOfLastPrefChange = TickCount();
- }
-
- static void PreferencesIdle(void)
- // Our idle function, called by DoIdle, once each time around
- // the event loop. We see how long the preferences have been dirty
- // and write them if it's been too long.
- {
- if ( TickCount() > gTicksOfLastPrefChange + kPrefChangeWriteDelay) {
- SavePreferences();
- }
- }
-
- static void TermPreferences(void)
- // This routine is called when the application quits
- // to save any preferences that haven't yet been saved by
- // our idle function and shut down our connection to IC.
- {
- OSStatus junk;
-
- if ( gICInstance != nil ) {
-
- if ( gTicksOfLastPrefChange != kPrefsCleanTicks ) {
- SavePreferences();
- }
-
- junk = ICStop(gICInstance);
- OTAssert("TermPreferences: Error closing IC", junk == noErr);
- gICInstance = nil;
- }
- }
-
- /////////////////////////////////////////////////////////////////////
- #pragma mark **** Startup and Shutdown Code *****
-
- static void InitMacToolbox(void)
- // Init all standard Mac OS managers.
- // How many times have I written this?
- {
- InitGraf(&qd.thePort);
- InitFonts();
- InitWindows();
- InitMenus();
- TEInit();
- InitDialogs(nil);
- MaxApplZone();
- InitCursor();
- }
-
- // InitOpenTransportWithMemoryLimit Big Picture
- // --------------------------------------------
- //
- // The LogEngine uses the OT memory allocation routines
- // (OTAllocMem, OTFreeMem) to allocate space for log entries in
- // the notifier. This memory comes from an ASLM memory pool
- // that OT creates for us when we call InitOpenTransport. However,
- // this pool has some bad characteristics:
- //
- // 1. The pool starts off very small, and only grows when we allocate
- // memory from it. As we do all our allocation from our notifier
- // (which is interrupt time with respect to the system Memory Manager)
- // the pool can't grow immediately. So the pool will often run
- // be full (ie OTAllocMem will return nil) even though the application
- // has plenty of memory. The pool will later grow, but we've already
- // dropped the log entry on the floor.
- //
- // 2. Because the pool starts off small and grows by pieces, we get
- // an extremely fragmented pool. While this works, its definitely
- // sub-optimal.
- //
- // 3. If we're being hammered by strlog (ie people are calling strlog a
- // lot), the pool will keep growing and there's nothing to stop
- // the pool consuming our entire application heap. When it does so,
- // various toolbox routines (eg QuickDraw) fail ungracefully, ie
- // SysError(25).
- //
- // There are a number of steps in my solution to this. The first step
- // is to call InitLibraryManager myself. This allows me to specify
- // the size of the pool. InitOpenTransport notices that I have inited ASLM
- // myself and doesn't do it itself. Thus OTAllocMem gets its memory from
- // whatever pool ASLM created. This gets around problems 1 and 2.
- //
- // The second step is to create a subsidiary zone within my application heap
- // and specify that ASLM should create its pool in that zone (by supplying
- // kCurrentZone to InitLibraryManager). Thus the pool can grow up to the
- // point where the memory in the subsidiary zone is exhausted. At that point,
- // the pool will no longer grow. So the pool will not steal memory from
- // the main application heap. This gets around problem 3.
- //
- // There are a number of other ways I could have achieved the same results.
- // The ASLM memory manager is very flexible. For example, I could have removed
- // OT's TPoolNotifier from the pool, which would prevent the pool from growing.
- // However, this solution does not require me to use any ASLM C++ stuff,
- // which makes the code more compiler independent.
-
- enum {
- kBytesReservedForToolboxInApplicationZone = 100L * 1024L,
- // This value represents the minimum number of contiguous
- // bytes that should remain in the application heap after
- // we've created the subsidiary zone.
-
- kBytesReservedForASLMInSubsidaryzone = 2048,
- // This value represents the number of bytes in the subsidiary
- // zone we should leave lying around for general purpose ASLM
- // use. The remaining bytes in the subsidiary zone are
- // dedicated to the ASLM memory pool, ie are passed as the pool
- // size to InitLibraryManager.
-
- kMinimumBytesForUsInSubsidiaryZone = 10 * 1024
- // This value represents the minimum pool size we pass to
- // InitLibraryManager. If we can't create a pool of at least
- // this size, the application doesn't start up.
- };
-
- static OSStatus InitOpenTransportWithMemoryLimit(void)
- // See above for an explanation of the big picture here.
- {
- OSStatus err;
- SInt32 junkTotalFree;
- SInt32 contigFree;
- SInt32 zoneSize;
- Ptr gSubsidiaryZone;
- THz oldZone;
-
- // Debugger();
-
- // First call the system Memory Manager to determine the largest
- // contiguous block in the heap.
-
- PurgeSpace(&junkTotalFree, &contigFree);
-
- // If it's too small for our toolbox needs, bail out.
-
- err = noErr;
- if (contigFree < kBytesReservedForToolboxInApplicationZone) {
- err = memFullErr;
- }
-
- // Now calculate the size of the zone we're going to create.
- // It's the size of the largest contiguous block, minus
- // the size of we reserve for toolbox needs, rounded to the nearest KB.
- // If the zone size isn't big enough enough to hold our minimum
- // pool size and the amount we reserve for ASLM, bail out.
-
- if (err == noErr) {
- zoneSize = contigFree - kBytesReservedForToolboxInApplicationZone;
- zoneSize = zoneSize & ~0x003FF;
- if (zoneSize < (kBytesReservedForASLMInSubsidaryzone + kMinimumBytesForUsInSubsidiaryZone)) {
- err = memFullErr;
- }
- }
-
- // Allocate the memory for our zone and create a zone in that
- // block. Then init ASLM, telling it to create a pool that
- // takes up the entire zone (minus the ASLM overhead factor)
- // in the current zone, ie the zone we just created. Finally,
- // initialise OT. OT will see that we've inited ASLM and use
- // the pool that ASLM created (in the zone we created) for
- // satisfying OTAllocMem requests.
-
- if (err == noErr) {
- gSubsidiaryZone = NewPtr(zoneSize);
- OTAssert("InitApplication: Couldn't get the memory but preflight says its there", gSubsidiaryZone != nil);
- OTAssert("InitApplication: Just being paranoid", MemError() == noErr);
-
- oldZone = GetZone();
-
- InitZone(nil, 16, gSubsidiaryZone + zoneSize, gSubsidiaryZone);
- OTAssert("InitApplication: InitZone failed", MemError() == noErr);
-
- // InitZone sets the current zone to the newly created zone,
- // so I don't have to do it myself.
-
- err = InitLibraryManager(zoneSize - kBytesReservedForASLMInSubsidaryzone, kCurrentZone, kNormalMemory);
- if (err == noErr) {
- err = InitOpenTransport();
- if (err != noErr) {
- CleanupLibraryManager();
- }
- }
-
- SetZone(oldZone);
- }
-
- // This code is used to artificially consume all memory
- // available through OTAllocMem. I used this to test
- // that the above strategy works. It's commented out now
- // because I'm happy that code works, but I want the code
- // around just in case I need to look at this situation again.
-
- #define TestByExhaustingOTMemoryPool 0
- #if TestByExhaustingOTMemoryPool
- if (err == noErr) {
- void *junk;
-
- do {
- junk = OTAllocMem(55);
- } while (junk != nil);
- Debugger();
- }
- #endif
-
- return err;
- }
-
- static OSStatus InitApplication(void)
- // Initialises the application-specific stuff.
- {
- OSStatus err;
- OSStatus junk;
- Handle mbarH;
- MenuHandle menuH;
- Rect viewRect;
- Rect dataBounds;
- Cell cellSize;
-
- // Initialise the Internet Config libraries that we use. Note
- // that these are different from our connection to the Internet
- // Config extension, which is setup in RestorePreferences. This
- // stuff is source code from IC (which is public domain, yay!) that
- // we've compiled and linked into our application for doing things
- // like managing List Manager lists, driving dialogs, etc.
-
- InitICDialogs();
- InitListManagerMiscSubs();
-
- // Setup the menu bar.
-
- mbarH = GetNewMBar(rMenuBar);
- OTAssert("InitApplication: Could not get menu bar", mbarH != nil);
- SetMenuBar(mbarH);
- InvalMenuBar();
-
- // Setup the Apple menu.
-
- menuH = GetMenuHandle(mApple);
- OTAssert("InitApplication: Could not get Apple menu", menuH != nil);
- AppendResMenu(menuH, 'DRVR');
-
- // Install our AppleEvent handlers.
-
- junk = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, NewAEEventHandlerProc(AEOpenApplicationHandler), 0, false);
- OTAssert("InitApplication: Could not install event handler", junk == noErr);
- junk = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, NewAEEventHandlerProc(AEOpenDocumentsHandler), 0, false);
- OTAssert("InitApplication: Could not install event handler", junk == noErr);
- junk = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerProc(AEQuitApplicationHandler), 0, false);
- OTAssert("InitApplication: Could not install event handler", junk == noErr);
-
- // Create the main window.
-
- gMainWindow = GetNewDialog(rMainDialog, nil, (WindowPtr) -1L);
- OTAssert("InitApplication: Could not create main window", gMainWindow != nil);
-
- gMainWindowGrowBounds.top = gMainWindow->portRect.bottom;
- gMainWindowGrowBounds.left = gMainWindow->portRect.right;
- gMainWindowGrowBounds.bottom = qd.screenBits.bounds.bottom;
- gMainWindowGrowBounds.right = qd.screenBits.bounds.right;
-
- // And the list that's in the main window.
-
- gListDefProcUPP = NewListDefProc(ListDefProc);
- OTAssert("InitApplication: Could not create UPP for ListDefProc", gListDefProcUPP != nil);
-
- GetDItemRect(gMainWindow, ditLogEntryList, &viewRect);
- viewRect.bottom -= 15;
- viewRect.right -= 15;
- SetRect(&dataBounds, 0, 0, 1, 0);
- // SetPt(&cellSize, viewRect.right - viewRect.left, 16);
- SetPt(&cellSize, 1000, 16);
- gLogList = LNew(&viewRect, // rView
- &dataBounds, // dataBounds
- cellSize, // cSize
- rSimpleLDEF, // theProc
- gMainWindow, // theWindow
- true, // drawIt
- true, // hasGrow
- true, // scrollHoriz
- true); // scrollVert
- OTAssert("InitApplication: Could not create ListHandle", gLogList != nil);
- (**gLogList).refCon = (long) gListDefProcUPP;
-
- gListUserItemUpdateUPP = NewUserItemProc(ListUserItemUpdateProc);
- OTAssert("InitApplication: Could not create UPP for ListUserItemUpdateProc", gListUserItemUpdateUPP != nil);
-
- SetDItemHandle(gMainWindow, ditLogEntryList, (Handle) gListUserItemUpdateUPP);
-
- // Restore our saved preferences, or setup defaults if
- // we have none.
-
- RestorePreferences();
-
- // Sync the Start and Stop buttons with the state of
- // the back end.
-
- UpdateStartStopUI();
-
- // Startup Open Transport. We have to do some special
- // things to prevent OT eating our entire heap. See
- // the comments associated with this routine.
-
- err = InitOpenTransportWithMemoryLimit();
-
- return err;
- }
-
- static void TermApplication(void)
- // Shut down our application. This is normal quitting,
- // ie via AppleEvent or via menu command. The main event
- // loop has already shut down the logging stuff. All we
- // need to do is close down the connection to Open Transport,
- // ASLM and Internet Config.
- //
- // Note that we make no attempt to clean up our toolbox stuff
- // or memory allocates. The Process Manager will do that for us
- // and there's no point writing extra code (and extra bugs) to
- // duplicate that here.
- {
- CloseOpenTransport();
- CleanupLibraryManager();
- TermPreferences();
-
- OTAssert("TermApplication: Quitting with raw streams open!!!", ! LoggingActive() );
- }
-
- #if GENERATINGCFM
-
- // Raw streams are not tracked by Open Transport. When the
- // application quits abnormally, we have to make sure that
- // we have closed the stream, otherwise it will dangle around
- // in memory. This would not be too bad except:
- //
- // a) the notifier may be called, calling code that is no
- // no longer loaded, and
- // b) the log driver won't let another client hook up as
- // the stream logger as long as the stream is open.
- //
- // So we have a CFM terminate procedure that shuts down
- // the raw stream if it's open.
- //
- // Note that I don't recommend this approach for closing
- // endpoints and such. OT has automatic shutdown code,
- // and I recommend that you rely on it for emergency shutdown
- // in the general case. This code is only necessary because
- // we're using raw streams.
- //
- // Of course, for normal shutdown, ie the user chooses Quit, you
- // should not rely on OT's emergency shutdown support; you
- // should always call CloseOpenTransport yourself for normal
- // shutdown.
-
- extern pascal void EmergencyShutdown(void);
-
- extern pascal void EmergencyShutdown(void)
- {
- if ( LoggingActive() ) {
- OTDebugStr("EmergencyShutdown: Had to StopLogging");
- StopLogging();
- }
- }
-
- #else
-
- // I could do the equivalent for the 68K case but it's not as
- // easy and I don't expect there to be many people using this
- // application on a 68K.
-
- #endif
-
- /////////////////////////////////////////////////////////////////////
- #pragma mark **** The Main Line! *****
-
- extern void main(void)
- {
- OSStatus err;
-
- // We make lots of calls to the OT raw streams API, which
- // is not available to emulated code on the PPC. So if we're
- // generating 68K code, we insert a check to prevent us
- // running emulated.
-
- #if ! GENERATINGCFM
- {
- SInt32 response;
-
- if ( Gestalt(gestaltSysArchitecture, &response) == noErr &&
- response == gestaltPowerPC ) {
- OTDebugStr("main: No sneaky running 68K on PPC!");
- ExitToShell();
- }
- }
- #endif
-
- InitMacToolbox();
- err = InitApplication();
- OTAssert("main: InitApplication returned an error", err == noErr);
- if (err == noErr) {
-
- MainEventLoop();
-
- TermApplication();
- }
- }
-